package cn.virens.service.impl;

import java.util.Collection;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.virens.CacheKeys;
import cn.virens.database.enums.UserType;
import cn.virens.database.enums.log.LogLevel;
import cn.virens.database.enums.log.LogType;
import cn.virens.database.mysql.mapper.sys.SysLogMapper;
import cn.virens.database.mysql.mapper.sys.SysUserShiroMapper;
import cn.virens.database.mysql.model.sys.SysLog;
import cn.virens.database.mysql.model.sys.SysResource;
import cn.virens.database.mysql.model.sys.SysUser;
import cn.virens.web.components.beetl.menu.common.MenuBuilder;
import cn.virens.web.components.beetl.menu.common.MenuProvider;
import cn.virens.web.components.shiro.ShiroAuthInterface;
import cn.virens.web.components.shiro.token.ChannelResult;
import cn.virens.web.components.shiro.token.ChannelToken;

@Service(ShiroAuthInterface.SHIRO_BEAN_NAME)
public class ShiroServiceImpl implements ShiroAuthInterface, MenuProvider {
	protected final Logger logger = LoggerFactory.getLogger(ShiroServiceImpl.class);

	private @Autowired SysUserShiroMapper mSysUserShiroMapper;
	private @Autowired SysLogMapper mSysLogMapper;

	@Override
	@Transactional(readOnly = true)
	public ChannelResult getUserToken(ChannelToken token) {
		ChannelResult result = mSysUserShiroMapper.selectLogin(token);

		// 如果登录使用的用户类型为Oauth的，则密码使用对应的类型名称
		if (StrUtil.equals(token.getUsertype(), UserType.GITEE.key())) {
			result.setPasswordSrc(UserType.GITEE.name());
		} else if (StrUtil.equals(token.getUsertype(), UserType.WEIXIN.key())) {
			result.setPasswordSrc(UserType.WEIXIN.name());
		} else if (StrUtil.equals(token.getUsertype(), UserType.WEIXIN_1.key())) {
			result.setPasswordSrc(UserType.WEIXIN_1.name());
		} else if (StrUtil.equals(token.getUsertype(), UserType.WEIXIN_2.key())) {
			result.setPasswordSrc(UserType.WEIXIN_2.name());
		}

		return result;
	}

	@Override
	@Transactional(readOnly = true)
	public Collection<String> getRoles(Object principal) {
		return mSysUserShiroMapper.selectRoleIdentity(String.valueOf(principal));
	}

	@Override
	public Collection<String> getGroups(Object principal) {
		return mSysUserShiroMapper.selectGroupIdentity(String.valueOf(principal));
	}

	@Override
	@Transactional(readOnly = true)
	public Collection<String> getResources(Object principal) {
		return mSysUserShiroMapper.selectResourceIdentity(String.valueOf(principal));
	}

	@Override
	public void onLoginSuccess(String username, String host) {
		logger.debug("用户登录成功({}):{}", username, host);

		SysLog record = new SysLog();
		record.setLevel(LogLevel.INFO.getKey());
		record.setType(LogType.LOGIN.getKey());
		record.setCreateTime(DateUtil.date());
		record.setUpdateTime(DateUtil.date());
		record.setContent("用户登录成功");
		record.setUser(username);
		record.setHost(host);

		mSysLogMapper.insertSelective(record);
	}

	@Override
	public void onLoginFailure(String username, String host) {
		logger.error("用户登录失败({}):{}", username, host);

		SysLog record = new SysLog();
		record.setLevel(LogLevel.ERROR.getKey());
		record.setType(LogType.LOGIN.getKey());
		record.setCreateTime(DateUtil.date());
		record.setUpdateTime(DateUtil.date());
		record.setContent("用户登录失败");
		record.setUser(username);
		record.setHost(host);

		mSysLogMapper.insertSelective(record);
	}

	@Override
	public SysUser getUser(Object principal) {
		return mSysUserShiroMapper.selectUser(String.valueOf(principal));
	}

	@Override
	@Cacheable(cacheNames = CacheKeys.CACHE_MENUS, key = "#p0 + '.' + #p1", unless = "#result == null")
	public String renderMenus(String uuid, String identity) {
		MenuBuilder builder = new MenuBuilder(identity, 0L);
		builder.append("<ul class=\"layui-nav layui-nav-tree\">");

		// 查询数据库中的菜单列表
		List<SysResource> resources = mSysUserShiroMapper.selectMenus(uuid);
		if (resources == null || resources.isEmpty()) return "菜单为空!";

		return builder.append(resources.stream().filter((a) -> builder.children(a.getPid())).map((a) -> {
			MenuBuilder parent = new MenuBuilder(identity, a.getId());

			// 查找下级菜单列表 & 并渲染子菜单
			resources.stream().filter((b) -> parent.children(b.getPid())).forEach((b) -> {
				MenuBuilder children = new MenuBuilder(identity, b.getId());
				children.append("<dd class=\"", children.style(b.getIdentity()), "\">");
				children.append("	<a href=\"", children.linkurl(b.getLinkurl()), "\">");
				children.append("		<i class=\"", children.icon(b.getIcon()), "\"></i>");
				children.append("		<span>", children.name(b.getName()), "</span>");
				children.append("	</a>");
				children.append("</dd>");

				// 检查菜单激活状态 & 添加到父级列表
				parent.setActive(b.getIdentity());
				parent.addChildren(children);
			});

			// 判断菜单是否拥有下级菜单 & 如果拥有就使用二级菜单方式渲染 & 否则使用一级菜单渲染
			if (parent.childrens() != null && !parent.childrens().isEmpty()) {
				parent.append("<li class=\"layui-nav-item ", parent.style(false), "\">");
				parent.append("	<a href=\"", parent.linkurl(a.getLinkurl()), "\">");
				parent.append("		<i class=\"", parent.icon(a.getIcon()), "\"></i>");
				parent.append("		<span>", parent.name(a.getName()), "</span>");
				parent.append("		<span class=\"layui-nav-more\"></span>");
				parent.append("	</a>");
				parent.append("	<dl class=\"layui-nav-child\">");
				parent.append(parent.childrens());
				parent.append("	</dl>");
				parent.append("</li>");
			} else {
				parent.append("<li class=\"layui-nav-item ", parent.style(a.getIdentity()), "\">");
				parent.append("	<a href=\"", parent.linkurl(a.getLinkurl()), "\">");
				parent.append("		<i class=\"", parent.icon(a.getIcon()), "\"></i>");
				parent.append("		<span>", parent.name(a.getName()), "</span>");
				parent.append("	</a>");
				parent.append("</li>");
			}

			return parent;
		})).append("</ul>").toString();
	}
}
